﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Data;
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Xml;
using System.Xml.Serialization;

namespace PageAdmin.Utils
{
    /// <summary>
    /// xml文件相关帮助类
    /// </summary>
    public class XmlHelper
    {

        /// <summary>
        /// 加载xml文档
        /// </summary>
        /// <param name="xmlPath"></param>
        /// <param name="cache">是否把Xml文档加入到缓存中,默认采用MemberCache</param>
        /// <returns></returns>
        public static XmlDocument LoadXmlDocument(string xmlPath, bool cache = false)
        {
            xmlPath = IOHelper.MapPath(xmlPath).ToLower();
            XmlDocument xmlDoc = null;
            //string cacheKey = EncryptHelper.MD5(Path.GetDirectoryName(xmlPath)) + "_" + Path.GetFileName(xmlPath);
            string cacheKey = xmlPath;
            if (cache)
            {
                if (CacheHelper.Contains(cacheKey))
                {
                    xmlDoc = CacheHelper.Get<XmlDocument>(cacheKey);
                }
            }
            if (xmlDoc == null)
            {
                xmlDoc = new XmlDocument();
                xmlDoc.XmlResolver = null;//禁止解析外部资源，避免XXE攻击
                xmlDoc.Load(xmlPath);
                if (cache)
                {
                    CacheHelper.Add(cacheKey, xmlDoc, xmlPath);
                }
            }
            return xmlDoc;
        }

        /// <summary>
        /// 深拷贝，利于XmlSerializer
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static T DeepClone<T>(T obj)
        {
            using (Stream stream = new MemoryStream())
            {
                XmlSerializer serializer = new XmlSerializer(typeof(T));
                serializer.Serialize(stream, obj);
                stream.Seek(0, SeekOrigin.Begin);
                return (T)serializer.Deserialize(stream);
            }
        }


        /// <summary>
        /// 获取一个节点。
        /// </summary>
        /// <param name="nodePath"></param>
        /// <param name="xmlPath"></param>
        /// <returns></returns>
        public static XmlNode GetXmlNode(string nodePath, string xmlPath)
        {
            XmlDocument doc = LoadXmlDocument(xmlPath);
            return doc.SelectSingleNode(nodePath);
        }

        /// <summary>
        /// 判断节点是否存在
        /// </summary>
        /// <param name="nodePath"></param>
        /// <param name="xmlPath"></param>
        /// <returns></returns>
        public static bool IsExists(string nodePath, string xmlPath)
        {
            XmlDocument doc = LoadXmlDocument(xmlPath);
            XmlNode xn = doc.SelectSingleNode(nodePath);
            return !(xn == null);
        }




        #region InnerText相关方法
        /// <summary>
        /// 获取xml中一个节点的内容,如果节点不存在则返回Null
        /// </summary>
        /// <param name="nodePath"></param>
        /// <param name="xmlPath"></param>
        /// <param name="cache">这里缓存只缓存整个XmlDocument文档，而不是具体的值</param>
        /// <returns></returns>
        public static string NodeValue(string nodePath, string xmlPath, bool cache = false) //NodePath必须为元素最终节点
        {
            string rv = "";
            XmlDocument doc = LoadXmlDocument(xmlPath, cache);
            XmlNode xn = doc.SelectSingleNode(nodePath);
            if (xn == null)
            {
                return null;
            }
            XmlElement xe = (XmlElement)(xn);
            rv = xe.InnerText;
            return rv;
        }


        /// <summary>
        /// 获取节点下指定多个节点的值
        /// </summary>
        /// <param name="nodeNames"></param>
        /// <param name="parentNodePath">节点名称，可指定多级节点，如node1/node2</param>
        /// <param name="xmlPath"></param>
        /// <param name="cache">这里缓存只缓存整个XmlDocument文档，而不是具体的值</param>
        /// <returns></returns>
        public static dynamic NodeValue(string[] nodeNames, string parentNodePath, string xmlPath, bool cache = false)
        {
            dynamic obj = new ExpandoObject();
            int nodeLength = nodeNames.Length;
            XmlDocument doc = LoadXmlDocument(xmlPath, cache);
            XmlElement xe;
            XmlNodeList xnlist = doc.SelectSingleNode(parentNodePath).ChildNodes;
            foreach (XmlNode node in xnlist)
            {
                if (node.NodeType != XmlNodeType.Element) { continue; }
                xe = (XmlElement)(node);
                for (int i = 0; i < nodeLength; i++)
                {
                    if (xe.Name == nodeNames[i])
                    {
                        ((IDictionary<string, object>)obj).Add(StringHelper.FirstCharUpper(xe.Name), xe.InnerText);
                        break;
                    }
                }
            }
            return obj;
        }


        /// <summary>
        /// 获取节点下所有子节点的,返回一个dynamic类型
        /// </summary>
        /// <param name="parentNodePath"></param>
        /// <param name="xmlPath"></param>
        /// <param name="cache">这里缓存只缓存整个XmlDocument文档，而不是具体的值</param>
        /// <returns></returns>
        public static dynamic AllNodeValue(string parentNodePath, string xmlPath, bool cache = false)
        {
            dynamic obj = new ExpandoObject();
            XmlDocument doc = LoadXmlDocument(xmlPath, cache);
            XmlElement xe;
            XmlNodeList xnlist = doc.SelectSingleNode(parentNodePath).ChildNodes;
            foreach (XmlNode node in xnlist)
            {
                if (node.NodeType != XmlNodeType.Element) { continue; }
                xe = (XmlElement)(node);
                ((IDictionary<string, object>)obj).Add(StringHelper.FirstCharUpper(xe.Name), xe.InnerText);
            }
            return obj;
        }

        /// <summary>
        /// 获取节点下所有子节点的,映射到T对象
        /// </summary>
        /// <param name="parentNodePath"></param>
        /// <param name="xmlPath"></param>
        /// <param name="cache">这里缓存只缓存整个XmlDocument文档，而不是具体的值</param>
        /// <returns></returns>
        public static T AllNodeValue<T>(string parentNodePath, string xmlPath, bool cache = false) where T :class,new()
        {
            XmlDocument doc = LoadXmlDocument(xmlPath, cache);
            XmlElement xe;
            XmlNodeList xnlist = doc.SelectSingleNode(parentNodePath).ChildNodes;
            Type type = typeof(T); //获取类型
            T obj = (T)Activator.CreateInstance(type); //反射创建实例
            var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);//获取所有特性
            foreach (XmlNode node in xnlist)
            {
                if (node.NodeType != XmlNodeType.Element) { continue; }
                xe = (XmlElement)(node);
                var key = xe.Name;
                var proinfo = properties.Where(c => c.Name.Equals(key, StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
                if (proinfo != null)
                {
                    if (proinfo.CanWrite)
                    {
                        var value = StringHelper.Format(xe.InnerText,proinfo.PropertyType);
                        proinfo.SetValue(obj, value);
                    }
                }
            }
            return obj;
        }

        /// <summary>
        /// 更新一个最终节点的值
        /// </summary>
        /// <param name="nodeName"></param>
        /// <param name="nodeValue"></param>
        /// <param name="xmlPath"></param>
        public static void UpdateNodeValue(string nodeName, string nodeValue, string xmlPath) //NodePath必须为元素最终节点
        {
            XmlDocument doc = LoadXmlDocument(xmlPath);
            XmlNode xn = doc.SelectSingleNode(nodeName);
            XmlElement xe = (XmlElement)(xn);
            xe.InnerText = nodeValue;
            doc.Save(IOHelper.MapPath(xmlPath));
        }

        /// <summary>
        /// 更新节点,通过字典集合匹配更新
        /// </summary>
        /// <param name="parentNode"></param>
        /// <param name="dicNodeValue"></param>
        /// <param name="xmlPath"></param>
        public static void UpdateNodeValue(string parentNode, IDictionary<string, string> dicNodeValue, string xmlPath)
        {
            XmlDocument doc = LoadXmlDocument(xmlPath);
            XmlElement xe;
            XmlNodeList xnlist = doc.SelectSingleNode(parentNode).ChildNodes;
            foreach (XmlNode node in xnlist)
            {
                if (node.NodeType != XmlNodeType.Element) { continue; }
                xe = (XmlElement)(node);
                foreach (KeyValuePair<string, string> item in dicNodeValue)
                {
                    if (xe.Name.Equals(item.Key, StringComparison.CurrentCultureIgnoreCase))
                    {
                        xe.InnerText = item.Value;
                        break;
                    }
                }
            }
            doc.Save(IOHelper.MapPath(xmlPath));
        }

        /// <summary>
        /// 更新节点,通过NameValueCollection总key对应更新，注意checkbox,Select如果没有选择会没有key导致不更新，这种情况一般可在NameValueCollection中构造一个值为空的key
        /// </summary>
        /// <param name="form"></param>
        /// <param name="nodePath"></param>
        /// <param name="xmlPath"></param>
        /// <param name="prefix"></param>
        public static void UpdateNodeValue(string nodePath, NameValueCollection form, string xmlPath, string prefix = "")
        {
            string[] keys = form.AllKeys;
            string[] ConfigKeys = keys.ToArray();
            if (ConfigKeys != null)
            {
                if (prefix != "")
                {
                    ConfigKeys = keys.Where((k) => k.IndexOf(prefix) == 0).ToArray();
                }
            }
            XmlDocument doc = LoadXmlDocument(xmlPath);
            XmlElement xe;
            XmlNodeList xnlist = doc.SelectSingleNode(nodePath).ChildNodes;
            foreach (XmlNode node in xnlist)
            {
                if (node.NodeType != XmlNodeType.Element) { continue; }
                xe = (XmlElement)(node);
                foreach (string key in ConfigKeys)
                {
                    string formName = key;
                    if (prefix != "")
                    {
                        formName = formName.Substring(prefix.Length);
                    }
                    if (xe.Name.Equals(formName, StringComparison.CurrentCultureIgnoreCase))
                    {
                        xe.InnerText = form[prefix + formName];
                        break;
                    }
                }
            }
            doc.Save(IOHelper.MapPath(xmlPath));
        }
        #endregion


        #region Attribute相关方法

        /// <summary>
        /// 获取xml中一个节点的属性的值,如果节点不存在或属性不存在则返回Null
        /// </summary>
        /// <param name="nodePath"></param>
        /// <param name="attributeName"></param>
        /// <param name="xmlPath"></param>
        /// <param name="cache"></param>
        /// <returns></returns>
        public static string NodeAttribute(string nodePath,string attributeName,string xmlPath, bool cache = false) //NodePath必须为元素最终节点
        {
            XmlDocument doc = LoadXmlDocument(xmlPath, cache);
            XmlNode xn = doc.SelectSingleNode(nodePath);
            if (xn == null)
            {
                return null;
            }
            XmlElement xe = (XmlElement)(xn);
            var attr = xe.Attributes[attributeName];
            if(attr==null)
            {
                return null;
            }
            return attr.Value;
        }

        /// <summary>
        /// 更新一个最终节点的的属性值
        /// </summary>
        /// <param name="nodePath"></param>
        /// <param name="attributeName"></param>
        /// <param name="attributeValue"></param>
        /// <param name="xmlPath"></param>
        public static void UpdateAttributeValue(string nodePath, string attributeName, string attributeValue, string xmlPath) //NodePath必须为元素最终节点
        {
            XmlDocument doc = LoadXmlDocument(xmlPath);
            XmlNode xn = doc.SelectSingleNode(nodePath);
            XmlElement xe = (XmlElement)(xn);
            xe.Attributes[attributeName].Value = attributeValue;
            doc.Save(IOHelper.MapPath(xmlPath));
        }
        #endregion

    }
}
